/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package CoalitionCirclePackage;

import GUIUtilities.ColorUtilities;
import GUIUtilities.IntVectorComparator;
import GUIUtilities.ListUtilities;
import SocialMatrixPackage.SocialMatrix;
import SwingAnimation.AnimationInterface;
import SwingAnimation.AnimationAnimator;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.Vector;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import multiarray.MultiArray;

/**
 *
 * @author Heather
 */
public class CoalitionCircleViewer extends JPanel implements AnimationInterface
{

  public enum SizeScalings
  {

    population, power
  };
  private Random randGen = new Random();
  // size of circles goes up as radius scale becomes bigger
  private static final double popGroupRadiusScale = 0.001;
  private static final double popCoalitionRadiusScale = 0.001;
  private static final double powerGroupRadiusScale = 0.001;
  private static final double powerCoalitionRadiusScale = 0.001;
  private static final int alpha = 140;
  private static final int maxColorValue = 170;
  private List<List<int[]>> coalitions;
  private MultiArray<Integer> pop;
  private MultiArray<Double> power;
  // scale by pop default
  private SizeScalings scaling = SizeScalings.population;
  private SocialMatrix sm;
  private IntVectorComparator comparator;
  // for drawing
  private Image buffer;
  private Graphics bufferG;
  //private HashMap<int[], CCPVGroupCircle> ascriptiveToCircleMap = new HashMap<int[], CCPVGroupCircle>();
  private HashMap<JMenuItem, CCPVGroupCircle> menuToCircleMap = new HashMap<JMenuItem, CCPVGroupCircle>();
  private JPopupMenu rightClickOptions = new JPopupMenu();
  //private JMenuItem seeConflictsOption = new JMenuItem("See conflicts with this coalition.");
  private Vector<JMenuItem> popupOptions = new Vector<JMenuItem>();
  private Vector<CCPVGroupCircle> selectedGroups = new Vector<CCPVGroupCircle>();
  private boolean menuOpen = false;
  private boolean showingConflicts = false;
  // this is the coalition we are focusing on to see conflict relationships
  private int conflictFocusCoalition = 0;
  private Color menuTransparencyColor = new Color(200, 200, 100, 70);
  private Vector<CCPVGroupCircle> allGroupCircles = new Vector<CCPVGroupCircle>();
  private Vector<CCPVCoalitionCircle> allCoalitionCircles = new Vector<CCPVCoalitionCircle>();
  private Vector<HashMap<CCPVGroupCircle, Point>> animationScript = new Vector<HashMap<CCPVGroupCircle, Point>>();
  private int animationIndex = 0;
  private Timer animationTimer = new Timer();
  private long animationStepInterval = 50;
  // default = 4 sec
  private static final long DEFAULT_ANIMATION_TIME = 4000;
  private long animationTotalTime = DEFAULT_ANIMATION_TIME;
  // this indicates if the animation is currently happening
  private boolean animationOn = false;
  // this indicates if the animation shows all steps or jumps to the end
  private boolean canAnimate = true;
  private boolean smSupplied = false;

  // just for testing
  public CoalitionCircleViewer()
  {
    sm = new SocialMatrix(new JTree());
    Vector<int[]> allCombos = sm.getAllCombinations();
    pop = new MultiArray(sm.getDimensions());
    for (int[] i : allCombos)
    {
      pop.setElementAtIndex(i, randGen.nextInt(1000) + 10);
    //pop.setElementAtIndex(i, 700);
    }
    comparator = new IntVectorComparator(pop);

    coalitions = new Vector<List<int[]>>();
    int numberOfCoalitions = randGen.nextInt(4) + 2;
    System.out.println("num coal: " + numberOfCoalitions);
    for (int i = 0; i < numberOfCoalitions; i++)
    {
      Vector<int[]> coalition = new Vector<int[]>();
      int numberOfGroups = 10;//randGen.nextInt(10) + 1;
      for (int j = 0; j < numberOfGroups; j++)
      {
        int[] group = allCombos.get(randGen.nextInt(allCombos.size()));
        coalition.add(group);
      }

      coalition = ListUtilities.sortByPop(coalition, comparator);
      coalitions.add(coalition);
    }

    coalitions = removeDuplicates(coalitions);
    setupDefaultCircles();
    rightClickOptions.setBackground(menuTransparencyColor);
    setCoalitionLocations();
    setupAllColors();
    setupListeners();
    this.setOpaque(false);
  }

  public CoalitionCircleViewer(List<List<int[]>> coalitions, SocialMatrix sm, MultiArray<Integer> pop)
  {
    smSupplied = true;
    this.sm = sm;
    this.pop = pop;
    comparator = new IntVectorComparator(pop);

    this.coalitions = new Vector<List<int[]>>();
    for (List<int[]> l : coalitions)
    {
      Vector<int[]> coalition = new Vector<int[]>();
      for (int[] i : l)
      {
        coalition.add(i);
      }
      this.coalitions.add(ListUtilities.sortByPop(coalition, comparator));
    }
    removeZeroPop(this.coalitions);
    this.coalitions = removeDuplicates(this.coalitions);

    setupDefaultCircles();
    rightClickOptions.setBackground(menuTransparencyColor);
    setCoalitionLocations();
    setupAllColors();
    setupListeners();
    this.setOpaque(false);
  }

  public CoalitionCircleViewer(List<List<int[]>> coalitions, SocialMatrix sm, MultiArray<Integer> pop, MultiArray<Double> power)
  {
    smSupplied = true;
    this.sm = sm;
    this.pop = pop;
    this.power = power;
    comparator = new IntVectorComparator(pop, power);

    this.coalitions = new Vector<List<int[]>>();
    for (List<int[]> l : coalitions)
    {
      Vector<int[]> coalition = new Vector<int[]>();
      for (int[] i : l)
      {
        coalition.add(i);
      }
      this.coalitions.add(ListUtilities.sortByPop(coalition, comparator));
    }
    removeZeroPop(this.coalitions);
    this.coalitions = removeDuplicates(this.coalitions);

    setupDefaultCircles();
    rightClickOptions.setBackground(menuTransparencyColor);
    setCoalitionLocations();
    setupAllColors();
    setupListeners();
    this.setOpaque(false);
  }

  private List<List<int[]>> removeDuplicates(List<List<int[]>> inputCoalitions)
  {
    List<List<int[]>> newCoal = new Vector<List<int[]>>();
    Vector<int[]> seenArrays = new Vector<int[]>();

    for (int i = 0; i < inputCoalitions.size(); i++)
    {
      List<int[]> coalition = inputCoalitions.get(i);
      List<int[]> newCoalition = new Vector<int[]>();

      for (int[] array : coalition)
      {
        boolean seen = false;

        for (int[] compare : seenArrays)
        {
          boolean equal = true;
          for (int j = 0; j < array.length; j++)
          {
            if (array[j] != compare[j])
            {
              equal = false;
            }
          }
          if (equal)
          {
            seen = true;
            break;
          }
        }

        if (!seen)
        {
          newCoalition.add(array);
          seenArrays.add(array);
        }
      }

      newCoal.add(newCoalition);
    }

    return newCoal;
  }

  private void setupDefaultCircles()
  {
    int coalitionNum = 0;
    for (List<int[]> v : coalitions)
    {
      int totalPop = 0;
      for (int[] i : v)
      {
        CCPVGroupCircle newCircle = new CCPVGroupCircle();
        newCircle.coalition = coalitionNum;
        newCircle.group = i;
        newCircle.population = pop.getElementAtIndex(i);
        newCircle.radius = (int) getGroupRadius(i);
        allGroupCircles.add(newCircle);

        totalPop += newCircle.population;
      }

      // add a new coalition circle too
      CCPVCoalitionCircle cc = new CCPVCoalitionCircle();
      cc.coalition = coalitionNum;
      cc.groups = v;
      cc.population = totalPop;
      cc.radius = (int) getCoalitionRadius(v);
      allCoalitionCircles.add(cc);

      coalitionNum++;
    }
  }

  private void setupListeners()
  {
    addMouseListener(new java.awt.event.MouseAdapter()
    {

      public void mouseClicked(java.awt.event.MouseEvent evt)
      {
      }

      public void mouseEntered(java.awt.event.MouseEvent evt)
      {
      }

      public void mouseExited(java.awt.event.MouseEvent evt)
      {
      }

      public void mousePressed(java.awt.event.MouseEvent evt)
      {
        Point clickP = evt.getPoint();

        // highlighting actions
        setAllColorsToDefault();
        selectedGroups = getAllContainingCircles(clickP);
        for (CCPVGroupCircle c : getSelectedGroups())
        {
          c.highlightOpaque();
        }

        // menu options
        if ((evt.getButton() != MouseEvent.BUTTON1) && (getSelectedGroups().size() > 0))
        {
          getRightClickOptions().removeAll();
          //getRightClickOptions().add(getSeeConflictsOption());
          getPopupOptions().clear();
          //getPopupOptions().add(getSeeConflictsOption());
          getMenuToCircleMap().clear();
          for (CCPVGroupCircle c : getSelectedGroups())
          {
            int[] pAscriptives = c.group;
            Vector<String> pAscriptiveVector = getSm().convertIndexIntoStringVector(pAscriptives);
            String pAscriptiveName = ListUtilities.getStringVectorString(pAscriptiveVector);
            JMenuItem newOption = new JMenuItem(pAscriptiveName);
            newOption.setBackground(getMenuTransparencyColor());
            getRightClickOptions().add(newOption);
            getPopupOptions().add(newOption);
            addListenerToMenuItem(newOption);
            getMenuToCircleMap().put(newOption, c);
          }

          CCPVGroupCircle firstSelection = getSelectedGroups().get(0);
          getSelectedGroups().clear();
          getSelectedGroups().add(firstSelection);

          getRightClickOptions().show(evt.getComponent(), clickP.x, clickP.y);
          menuOpen = true;
        }
        else
        {
          menuOpen = false;
        }

        repaint();
      }

      public void mouseReleased(java.awt.event.MouseEvent evt)
      {
        if (!isMenuOpen())
        {
          getSelectedGroups().clear();
        }
        repaint();
      }
    });

  /*    seeConflictsOption.addActionListener(new java.awt.event.ActionListener()
  {

  public void actionPerformed(java.awt.event.ActionEvent evt)
  {
  showingConflicts = !isShowingConflicts();

  if (!isShowingConflicts())
  {
  getSeeConflictsOption().setText("See conflicts with this coalition");
  }
  else
  {
  getSeeConflictsOption().setText("Turn off conflicts");
  }
  conflictFocusCoalition = getSelectedGroups().get(0).coalition;
  setAllProperColors();
  getSelectedGroups().clear();
  repaint();
  }
  });*/
  }

  private void addListenerToMenu()
  {
    rightClickOptions.addPropertyChangeListener(new java.beans.PropertyChangeListener()
    {

      public void propertyChange(java.beans.PropertyChangeEvent evt)
      {
      }
    });
  }

  private void addListenerToMenuItem(final JMenuItem item)
  {
    item.addMouseListener(new java.awt.event.MouseAdapter()
    {

      public void mouseEntered(java.awt.event.MouseEvent evt)
      {
        CCPVGroupCircle selected = getMenuToCircleMap().get(item);
        selected.highlightOpaque();
        getSelectedGroups().clear();
        getSelectedGroups().add(selected);
        repaint();
      }
    });
  }

  // returns the number of groups in this list that have people
  private List<List<int[]>> removeZeroPop(List<List<int[]>> input)
  {
    Vector<List<int[]>> ret = new Vector<List<int[]>>();

    for (List<int[]> l : input)
    {
      Vector<int[]> v = new Vector<int[]>();

      for (int[] i : l)
      {
        if (getPop(i) > 0)
        {
          v.add(i);
        }
      }

      if (v.size() > 0)
      {
        ret.add(v);
      }
    }

    return ret;
  }

  private void setCoalitionLocations()
  {
    HashMap<CCPVGroupCircle, Point> movePts = getAllGroupPoints(coalitions);

    for (CCPVGroupCircle c : allGroupCircles)
    {
      c.cornerLocation = movePts.get(c);
      Point groupCenter = new Point(c.cornerLocation.x + c.radius, c.cornerLocation.y + c.radius);
      c.centerLocation = groupCenter;
    }
  }

  private void setupAllColors()
  {
    setupGroupColors();
    setupCoalitionColors();
  }

  private void setupCoalitionColors()
  {
    // set coalition colors
    for (CCPVCoalitionCircle cc : allCoalitionCircles)
    {
      cc.defaultColor = getCoalitionColor(cc.groups);
      cc.currentColor = getCoalitionColor(cc.groups);
    }
  }

  private void setupGroupColors()
  {
    for (CCPVGroupCircle c : allGroupCircles)
    {
      if (c.defaultColor == null)
      {
        int red = randGen.nextInt(maxColorValue);
        int green = randGen.nextInt(maxColorValue);
        int blue = randGen.nextInt(maxColorValue);
        c.defaultColor = new Color(red, green, blue, alpha);
        c.currentColor = new Color(red, green, blue, alpha);
      }
    }
  }

  private HashMap<CCPVGroupCircle, Point> getAllGroupPoints(List<List<int[]>> inputCoalitions)
  {
    HashMap<CCPVGroupCircle, Point> ret = new HashMap<CCPVGroupCircle, Point>();
    setupCoalitionCircles(inputCoalitions);

    for (int i = 0; i < inputCoalitions.size(); i++)
    {
      Point lCenter = allCoalitionCircles.get(i).centerLocation;
      List<int[]> l = inputCoalitions.get(i);

      double numberOfLocations = l.size();
      double angleIncrement = Math.toRadians(360.0 / numberOfLocations);
      double currentAngle = Math.toRadians(0);
      // for each point, translate them.
      for (int[] group : l)
      {
        CCPVGroupCircle c = null;
        for (CCPVGroupCircle gc : allGroupCircles)
        {
          if (gc.containsGroups(group))
          {
            c = gc;
          }
        }

        c.radius = (int) getGroupRadius(c.group);
        int radius = c.radius;
        // first calculate the locations w/ respect to the origin for simplicity
        int relativeCenterY = (int) (Math.cos(currentAngle) * radius);
        int relativeCenterX = (int) (-Math.sin(currentAngle) * radius);
        int relativeCornerY = (int) (relativeCenterY - radius);
        int relativeCornerX = (int) (relativeCenterX - radius);
        currentAngle += angleIncrement;

        // now translate pts according to the coalition center
        int absoluteCornerX = relativeCornerX + lCenter.x;
        int absoluteCornerY = relativeCornerY + lCenter.y;
        Point groupCorner = new Point(absoluteCornerX, absoluteCornerY);

        ret.put(c, groupCorner);
      }
    }

    return ret;
  }

  private void setupCoalitionCircles(List<List<int[]>> inputCoalitions)
  {
    int numberOfLocations = inputCoalitions.size();
    double angleIncrement = Math.toRadians(360 / numberOfLocations);
    double currentAngle = 0;
    double radius = getWidth() / 4.0;

    allCoalitionCircles.clear();
    for (int i = 0; i < numberOfLocations; i++)
    {
      int y = (int) ((Math.sin(currentAngle) * radius) + (getWidth() / 2.0));
      int x = (int) ((Math.cos(currentAngle) * radius) + (getWidth() / 2.0));
      Point newPoint = new Point(x, y);

      // add a new coalition circle too
      CCPVCoalitionCircle cc = new CCPVCoalitionCircle();
      cc.coalition = i;
      cc.groups = inputCoalitions.get(i);
      cc.population = getCoalitionPop(cc.groups);
      cc.radius = (int) getCoalitionRadius(cc.groups);
      allCoalitionCircles.add(cc);

      cc.centerLocation = newPoint;
      cc.cornerLocation = new Point(x - cc.radius, y - cc.radius);

      currentAngle += angleIncrement;
    }
  }

  public Color getCoalitionColor(List<int[]> groups)
  {
    int redTotal = 0;
    int greenTotal = 0;
    int blueTotal = 0;

    for (CCPVGroupCircle c : allGroupCircles)
    {
      boolean found = false;
      int[] cArray = c.group;
      for (int[] i : groups)
      {
        boolean equal = true;
        for (int j = 0; j < i.length; j++)
        {
          if (i[j] != cArray[j])
          {
            equal = false;
          }
        }

        if (equal)
        {
          found = true;
        }
      }

      if (found)
      {
        Color cColor = c.defaultColor;
        redTotal += cColor.getRed();
        greenTotal += cColor.getGreen();
        blueTotal += cColor.getBlue();
      }
    }

    int red = (int) (redTotal / groups.size());
    int green = (int) (greenTotal / groups.size());
    int blue = (int) (blueTotal / groups.size());

    return new Color(red, green, blue, alpha);
  }

  private double getCoalitionRadius(List<int[]> groups)
  {
    double ret = 0.0;
    if (scaling == SizeScalings.population)
    {
      int totalPop = 0;
      for (int[] i : groups)
      {
        totalPop += getPop(i);
      }

      double preRoot = totalPop * popCoalitionRadiusScale / Math.PI;
      ret = Math.sqrt(preRoot);
    }
    else if (scaling == SizeScalings.power)
    {
      double totalPower = 0;
      for (int[] i : groups)
      {
        totalPower += getPower(i);
      }

      double preRoot = totalPower * powerCoalitionRadiusScale / Math.PI;
      ret = Math.sqrt(preRoot);
    }

    return ret;
  }

  private double getGroupRadius(int[] group)
  {
    if (scaling == SizeScalings.population)
    {
      double preRoot = getPop(group) * popGroupRadiusScale / Math.PI;
      return Math.sqrt(preRoot);
    }
    else if (scaling == SizeScalings.power)
    {
      double preRoot = getPower(group) * powerGroupRadiusScale / Math.PI;
      return Math.sqrt(preRoot);
    }
    else
    {
      return 0.0;
    }
  }

  private int getCoalitionPop(List<int[]> coalitionGroups)
  {
    int total = 0;
    for (int[] i : coalitionGroups)
    {
      total += getPop(i);
    }

    return total;
  }

  private int getPop(int[] group)
  {
    return pop.getElementAtIndex(group);
  }

  private double getPower(int[] group)
  {
    double powerScale = power.getElementAtIndex(group);
    int pop = getPop(group);

    return powerScale * pop;
  }

  // returns all circles locations that contain click
  private Vector<CCPVGroupCircle> getAllContainingCircles(Point click)
  {
    Vector<CCPVGroupCircle> ret = new Vector<CCPVGroupCircle>();

    for (CCPVGroupCircle c : allGroupCircles)
    {
      Point center = c.centerLocation;
      int radius = c.radius;
      int xDiff = click.x - center.x;
      int yDiff = click.y - center.y;
      double distance = Math.sqrt((xDiff * xDiff) + (yDiff * yDiff));
      if (distance < radius)
      {
        ret.add(c);
        System.out.println("distance: " + distance);
        System.out.println("radius: " + radius);
        System.out.println("center: " + c);
        System.out.println("click: " + click);
        System.out.println("********");
      }
    }

    return ret;
  }

  private Vector<CCPVGroupCircle> getOtherCoalitionCircles(Vector<CCPVGroupCircle> input)
  {
    Vector<CCPVGroupCircle> ret = new Vector<CCPVGroupCircle>();

    for (CCPVGroupCircle c : allGroupCircles)
    {
      boolean inSameCoalition = false;
      for (CCPVGroupCircle i : input)
      {
        if (c.coalition == i.coalition)
        {
          inSameCoalition = true;
        }
      }

      if (!inSameCoalition)
      {
        ret.add(c);
      }
    }

    return ret;
  }

  public void setAllColorsToDefault()
  {
    for (CCPVGroupCircle c : allGroupCircles)
    {
      c.setColorToDefault();
    }
  }

  public void setAllProperColors()
  {
    for (CCPVGroupCircle c : allGroupCircles)
    {
      if (selectedGroups.contains(c))
      {
        if (selectedGroups.size() > 1)
        {
          c.highlightLowAlpha();
        }
        else
        {
          c.highlightOpaque();
        }
      }
      else
      {
        if (showingConflicts && c.coalition != conflictFocusCoalition)
        {
          if (!smSupplied) // use default coloring if we don't have altruisms supplied
          {
            Color cDefaultC = c.defaultColor;
            int red = cDefaultC.getRed() + 80;
            int green = cDefaultC.getGreen() - 50;
            int blue = cDefaultC.getBlue() - 50;

            if (red > 255)
            {
              red = 255;
            }
            if (green < 0)
            {
              green = 0;
            }
            if (blue < 0)
            {
              blue = 0;
            }

            Color newColor = new Color(red, green, blue, alpha);
            c.currentColor = newColor;
          }
          else
          {
            Vector<int[]> cCoalition = (Vector<int[]>)allCoalitionCircles.get(c.coalition).groups;
            Vector<int[]> targetCoalition = (Vector<int[]>)allCoalitionCircles.get(conflictFocusCoalition).groups;
            double altruism = sm.getAverageAltruismMultipleSourcesAndTargetsWithPopulation(cCoalition, targetCoalition, pop);
            c.currentColor = ColorUtilities.getConflictColor(altruism);
          }
        }
        else
        {
          c.setColorToDefault();
        }
      }
    }
  }

  public String toString()
  {
    String ret = "";
    for (int i = 0; i < coalitions.size(); i++)
    {
      ret += "COALITION " + i + ":\n";
      List<int[]> coalition = coalitions.get(i);
      for (int[] group : coalition)
      {
        ret += "{";
        for (int g = 0; g < group.length - 1; g++)
        {
          ret += group[g] + ", ";
        }
        ret += group[group.length - 1] + ")\n";
      }
    }

    return ret;
  }

  public Color getDefaultColor(int[] group)
  {
    Vector<int[]> allCombos = sm.getAllCombinations();
    int numOfCombos = allCombos.size();
    int[] dim = sm.getDimensions();
    int iIndex = 0;
    for (int i = dim.length - 1; i >= 0; i--)
    {
    }

    double colorIncrement = Math.pow(maxColorValue, 3) / (numOfCombos - 1);
    double iColorBase10 = colorIncrement * iIndex;

    int red = 0;
    int green = 0;
    int blue = 0;

    double redCounter = Math.pow(maxColorValue, 2);
    if (iColorBase10 > redCounter)
    {
      red = (int) (iColorBase10 / redCounter);
      iColorBase10 -= (int) (red * redCounter);
    }

    double greenCounter = Math.pow(maxColorValue, 1);
    if (iColorBase10 > greenCounter)
    {
      green = (int) (iColorBase10 / greenCounter);
      iColorBase10 -= (int) (green * greenCounter);
    }

    if (iColorBase10 > 0)
    {
      blue = (int) iColorBase10;
    }

    return new Color(red, green, blue, alpha);
  }

  public void setSize(Dimension d)
  {
    super.setSize(d);
    setCoalitionLocations();
    setupAllColors();
  }

  public void setPreferredSize(Dimension d)
  {
    super.setSize(d);
    setCoalitionLocations();
    setupAllColors();
  }

  public void startAnimation()
  {
    animationTimer.scheduleAtFixedRate(new AnimationAnimator(this), 0, animationStepInterval);
    animationOn = true;
  }

  public void stopAnimation()
  {
    animationTimer.cancel();
    animationTimer = new Timer();
    animationOn = false;
    repaint();
  }

  public void beginAnimation()
  {
    animationIndex = 0;
    animationTimer.scheduleAtFixedRate(new AnimationAnimator(this), 0, animationStepInterval);
  }

  public boolean isCoalitionValid(List<List<int[]>> newCoalitions)
  {
    // first make sure the new coalition satisfies all requirements
    boolean valid = true;
    // make sure that the number of groups supplied is equal to the number of circles
    int totalGroups = 0;
    for (List<int[]> l : newCoalitions)
    {
      totalGroups += l.size();
    }
    if (totalGroups != allGroupCircles.size())
    {
      System.out.println("mismatch in size of coalition input vector and number of circles");
      valid = false;
    }

    // make sure each circle has a spot
    for (CCPVGroupCircle c : allGroupCircles)
    {
      boolean found = false;
      for (List<int[]> l : newCoalitions)
      {
        for (int[] array : l)
        {
          if (c.containsGroups(array))
          {
            found = true;
          }
        }
      }

      if (!found)
      {
        valid = false;
      }
    }

    return valid;
  }

  public List<List<int[]>> generateRandomCoalition()
  {
    int numberOfCoalitions = randGen.nextInt(5) + 1;
    int groupCircleIndex = 0;

    List<List<int[]>> newCoalitions = new Vector<List<int[]>>();
    for (int i = 0; i < numberOfCoalitions; i++)
    {
      List<int[]> coalition = new Vector<int[]>();
      coalition.add(allGroupCircles.get(i).group);
      allGroupCircles.get(i).coalition = i;
      newCoalitions.add(coalition);
      groupCircleIndex++;
    }

    // add remaining groups randomly
    for (int i = groupCircleIndex; i < allGroupCircles.size(); i++)
    {
      int coalitionIndex = randGen.nextInt(numberOfCoalitions);
      allGroupCircles.get(groupCircleIndex).coalition = coalitionIndex;
      newCoalitions.get(coalitionIndex).add(allGroupCircles.get(groupCircleIndex).group);
      groupCircleIndex++;
    }

    return newCoalitions;
  }

  public void setRandomCoalition()
  {
    setCoalitions(generateRandomCoalition());
    repaint();
  }

  public void setCoalitions(List<List<int[]>> newCoalitions)
  {
    List<List<int[]>> filteredCoalitions = removeDuplicates(newCoalitions);
    if (isCoalitionValid(filteredCoalitions))
    {
      coalitions = filteredCoalitions;

      // now sort the coalitions to make sure they go to the right place
      List<List<int[]>> sortedCoalitions = new Vector<List<int[]>>();
      for (List<int[]> l : filteredCoalitions)
      {
        List<int[]> sortedCoalition = ListUtilities.sortByPop(l, comparator);
        sortedCoalitions.add(sortedCoalition);
      }

      // set all circle parameters, including groups
      for (int i = 0; i < sortedCoalitions.size(); i++)
      {
        for (int[] group : sortedCoalitions.get(i))
        {
          for (CCPVGroupCircle c : allGroupCircles)
          {
            if (c.containsGroups(group))
            {
              c.coalition = i;
            }
          }
        }
      }

      HashMap<CCPVGroupCircle, Point> finishPts = getAllGroupPoints(sortedCoalitions);

      for (CCPVGroupCircle c : allGroupCircles)
      {
        Point moveP = finishPts.get(c);
        c.move(moveP);
      }

      setupCoalitionColors();
    }
  }

  public void clearAnimationScript()
  {
    animationTimer.cancel();
    animationTimer = new Timer();
    animationScript.clear();
    animationIndex = 0;
  }

  public void setupCoalitionFormationAnimation(List<List<int[]>> newCoalitions)
  {
    // first check that the input is indeed different
    if (ListUtilities.doPartitionsHaveSameElements(coalitions, newCoalitions))
    {
      animationScript.clear();
    }
    else
    {
      List<List<int[]>> filteredCoalitions = removeDuplicates(newCoalitions);
      if (isCoalitionValid(filteredCoalitions))
      {
        animationScript.clear();

        // now sort the coalitions to make sure they go to the right place
        List<List<int[]>> sortedCoalitions = new Vector<List<int[]>>();
        for (List<int[]> l : filteredCoalitions)
        {
          List<int[]> sortedCoalition = ListUtilities.sortByPop(l, comparator);
          sortedCoalitions.add(sortedCoalition);
        }

        // set all circle parameters, including groups
        for (int i = 0; i < sortedCoalitions.size(); i++)
        {
          for (int[] group : sortedCoalitions.get(i))
          {
            for (CCPVGroupCircle c : allGroupCircles)
            {
              if (c.containsGroups(group))
              {
                c.coalition = i;
              }
            }
          }
        }

        // now create all the points
        // first we need to find out how many "frames" there will be per interval
        int numberOfFrames = (int) (animationTotalTime / animationStepInterval);
        HashMap<CCPVGroupCircle, Point> finishPts = getAllGroupPoints(sortedCoalitions);
        // the point is really just to tell us how much to move each circle
        HashMap<CCPVGroupCircle, Point2D.Double> moveDistances = new HashMap<CCPVGroupCircle, Point2D.Double>();
        // this the current animation step we're constructing
        HashMap<CCPVGroupCircle, Point> currentAnimationStep = new HashMap<CCPVGroupCircle, Point>();
        // this is the double running total.  used to make sure that we aren't "losing pixels" due to rounding
        HashMap<CCPVGroupCircle, Point2D.Double> currentRunningTotal = new HashMap<CCPVGroupCircle, Point2D.Double>();

        if (canAnimate)  // only set incremented animation if animation is allowed
        {
          for (CCPVGroupCircle c : allGroupCircles)
          {
            Point cFinish = finishPts.get(c);
            double xIncrement = (double) (cFinish.x - c.cornerLocation.x) / (double) (numberOfFrames);
            double yIncrement = (double) (cFinish.y - c.cornerLocation.y) / (double) (numberOfFrames);
            moveDistances.put(c, new Point2D.Double(xIncrement, yIncrement));

            // add in the first step
            double newX = c.cornerLocation.x + xIncrement;
            double newY = c.cornerLocation.y + yIncrement;
            Point newPoint = new Point((int) newX, (int) newY);
            Point2D.Double newRunningPoint = new Point2D.Double(newX, newY);

            currentAnimationStep.put(c, newPoint);
            currentRunningTotal.put(c, newRunningPoint);
          }
          animationScript.add(currentAnimationStep);
          currentAnimationStep = new HashMap<CCPVGroupCircle, Point>();

          // first step already done, so we start at 1 to add remaining frames
          for (int i = 1; i < numberOfFrames; i++)
          {
            for (CCPVGroupCircle c : allGroupCircles)
            {
              Point2D.Double moveDistance = moveDistances.get(c);
              Point2D.Double cNotRounded = currentRunningTotal.get(c);
              cNotRounded.x += moveDistance.x;
              cNotRounded.y += moveDistance.y;

              Point newPoint = new Point((int) cNotRounded.x, (int) cNotRounded.y);
              currentAnimationStep.put(c, newPoint);
            }

            animationScript.add(currentAnimationStep);
            currentAnimationStep = new HashMap<CCPVGroupCircle, Point>();
          }
        }
        else // only make the animation jump to the last frame (the finish pts)
        {
          animationScript.add(finishPts);
        }

        // lastly, since the coalitions are different, we need to adjust the colors
        setupCoalitionColors();
      }
      else
      {
        System.out.println("supplied new coalitions do not match current coalitions");
      }
    }
  }

  public void setupCoalitionFormationAnimationWithInitialPause(List<List<int[]>> newCoalitions)
  {
    setupCoalitionFormationAnimation(newCoalitions);
    // time to add in the pause
    int pauseTime = 800;
    int pauseIntervals = (int) (pauseTime / animationStepInterval);
    for (int i = 0; i < pauseIntervals; i++)
    {
      HashMap<CCPVGroupCircle, Point> currentAnimationStep = new HashMap<CCPVGroupCircle, Point>();

      for (CCPVGroupCircle c : allGroupCircles)
      {
        currentAnimationStep.put(c, c.cornerLocation);
      }

      animationScript.add(0, currentAnimationStep);
    }
  }

  public List<List<int[]>> createTestAnimation()
  {
    List<List<int[]>> ret = new Vector<List<int[]>>();

    Vector<int[]> coalition1 = new Vector<int[]>();
    Vector<int[]> coalition2 = new Vector<int[]>();

    for (List<int[]> l : coalitions)
    {
      for (int[] i : l)
      {
        if (randGen.nextDouble() > 0.5)
        {
          coalition1.add(i);
        }
        else
        {
          coalition2.add(i);
        }
      }
    }
    ret.add(coalition1);
    ret.add(coalition2);

    return ret;
  }

  public void createTestAnimation2()
  {
    animationScript.clear();
    CCPVGroupCircle toMove = allGroupCircles.get(randGen.nextInt(allGroupCircles.size() - 1));
    Point currentMovePt = toMove.centerLocation;

    for (int i = 0; i < 180; i++)
    {
      HashMap<CCPVGroupCircle, Point> movePts = new HashMap<CCPVGroupCircle, Point>();
      for (CCPVGroupCircle c : allGroupCircles)
      {
        if (c.equals(toMove))
        {
          Point newPt = new Point(currentMovePt.x - 1, currentMovePt.y - 1);
          currentMovePt = newPt;

          movePts.put(toMove, newPt);
        }
        else
        {
          movePts.put(c, c.cornerLocation);
        }
      }

      animationScript.add(movePts);
    }
  }

  // returns false if the animation is finished
  public boolean showNextAnimation()
  {
    if (animationIndex < animationScript.size())
    {
      HashMap<CCPVGroupCircle, Point> step = animationScript.get(animationIndex);
      for (CCPVGroupCircle c : allGroupCircles)
      {
        c.move(step.get(c));
      }

      animationIndex++;
      return true;
    }
    else
    {
      animationIndex = 0;
      animationTimer.cancel();
      animationOn = false;
      repaint();
      return false;
    }
  }

  public String getSelectedGroupString()
  {
    System.out.println(selectedGroups.size());
    String ret = "";

    if (selectedGroups.size() > 1)
    {
      ret += "[" + getGroupVectorString(selectedGroups.get(0).group) + ",\n";
      for (int i = 1; i < selectedGroups.size() - 1; i++)
      {
        ret += " " + getGroupVectorString(selectedGroups.get(i).group) + ",\n";
      }

      ret += " " + getGroupVectorString(selectedGroups.get(selectedGroups.size() - 1).group) + "]";
    }
    else
    {
      ret += getGroupVectorString(selectedGroups.get(0).group);
    }

    return ret;
  }

  private String getGroupVectorString(int[] group)
  {
    String ret = "[";
    Vector<String> stringV = sm.convertIndexIntoStringVector(group);

    for (int i = 0; i < stringV.size() - 1; i++)
    {
      ret += stringV.get(i) + ", ";
    }
    ret += stringV.get(stringV.size() - 1) + "]";

    return ret;
  }

  public void sortCirclesByPop()
  {
    for (List<int[]> coalition : coalitions)
    {
      coalition = ListUtilities.sortByPop(coalition, comparator);
    }
    organizeCirclesByCoalitions();
  }

  public void sortCirclesByPower()
  {
    for (List<int[]> coalition : coalitions)
    {
      coalition = ListUtilities.sortByPower(coalition, comparator);
    }
    organizeCirclesByCoalitions();
  }

  private void organizeCirclesByCoalitions()
  {
    Vector<CCPVGroupCircle> newCircles = new Vector<CCPVGroupCircle>();
    for (List<int[]> coalition : coalitions)
    {
      for (int[] group : coalition)
      {
        newCircles.add(getCircleFromCharacteristics(group));
      }
    }

    allGroupCircles = newCircles;
  }

  private CCPVGroupCircle getCircleFromCharacteristics(int[] group)
  {
    for (CCPVGroupCircle c : allGroupCircles)
    {
      if (c.containsGroups(group))
      {
        return c;
      }
    }

    return null;
  }

  private void drawCircles(Graphics g)
  {
    setAllProperColors();
    //g.setColor(Color.white);
    //g.fillRect(0, 0, this.getWidth(), this.getHeight());

    // paint coalition circles first, so they are on the bottom
    if (!animationOn)
    {
      for (CCPVCoalitionCircle c : allCoalitionCircles)
      {
        //bufferG.setColor(c.currentColor);
        g.setColor(c.currentColor);
        int diameter = c.radius * 2;
        Point p = c.cornerLocation;
        //bufferG.drawOval(p.x, p.y, diameter, diameter);
        g.drawOval(p.x, p.y, diameter, diameter);

        // draw more ovals to make it slightly thicker
        diameter--;
        //bufferG.drawOval(p.x + 1, p.y + 1, diameter, diameter);
        g.drawOval(p.x + 1, p.y + 1, diameter, diameter);
        diameter--;
        //bufferG.drawOval(p.x + 1, p.y + 1, diameter, diameter);
        g.drawOval(p.x + 1, p.y + 1, diameter, diameter);
        diameter--;
        //bufferG.drawOval(p.x + 2, p.y + 2, diameter, diameter);
        g.drawOval(p.x + 2, p.y + 2, diameter, diameter);
      }
    }

    for (CCPVGroupCircle c : allGroupCircles)
    {
      if (!selectedGroups.contains(c))
      {
        //bufferG.setColor(c.currentColor);
        g.setColor(c.currentColor);
        int diameter = c.radius * 2;
        Point p = c.cornerLocation;
        //bufferG.fillOval(p.x, p.y, diameter, diameter);
        g.fillOval(p.x, p.y, diameter, diameter);
      }
    }

    // paint selected last, so they are on top
    for (CCPVGroupCircle c : selectedGroups)
    {
      //bufferG.setColor(c.currentColor);
      g.setColor(c.currentColor);
      int diameter = c.radius * 2;
      Point p = c.cornerLocation;
      //bufferG.fillOval(p.x, p.y, diameter, diameter);
      g.fillOval(p.x, p.y, diameter, diameter);
    }

  }

  public void paint(Graphics g)
  {
    // This creates the buffer
    if (buffer == null)
    {
      int xBound = getSize().width;
      int yBound = getSize().height;

      buffer = this.createImage(xBound, yBound);
      bufferG = buffer.getGraphics();
    //bufferG.setColor(Color.white);
    //bufferG.fillRect(0, 0, xBound, yBound);
    }
    //g.drawImage(buffer, 0, 0, this);
    drawCircles(g);

    for (Component c : this.getComponents())
    {
      c.repaint();
    }
  }

  public void paintAnimation()
  {
    repaint();
  }

  public void toggleAnimation()
  {
    canAnimate = !canAnimate;
  }

  public void setScaling(SizeScalings s)
  {
    scaling = s;
    // sort according to settings
    /*
    if (scaling == SizeScalings.population)
    {
    sortCirclesByPop();
    }
    else if (scaling == SizeScalings.power)
    {
    sortCirclesByPower();
    }
     */
    setCoalitionLocations();
    setupAllColors();
    repaint();
  }

  /**
   * @return the randGen
   */
  public Random getRandGen()
  {
    return randGen;
  }

  /**
   * @return the coalitions
   */
  public List<List<int[]>> getCoalitions()
  {
    return coalitions;
  }

  /**
   * @return the pop
   */
  public MultiArray<Integer> getPop()
  {
    return pop;
  }

  /**
   * @return the sm
   */
  public SocialMatrix getSm()
  {
    return sm;
  }

  /**
   * @return the comparator
   */
  public IntVectorComparator getComparator()
  {
    return comparator;
  }

  /**
   * @return the buffer
   */
  public Image getBuffer()
  {
    return buffer;
  }

  /**
   * @return the bufferG
   */
  public Graphics getBufferG()
  {
    return bufferG;
  }

  /**
   * @return the menuToCircleMap
   */
  public HashMap<JMenuItem, CCPVGroupCircle> getMenuToCircleMap()
  {
    return menuToCircleMap;
  }

  /**
   * @return the rightClickOptions
   */
  public JPopupMenu getRightClickOptions()
  {
    return rightClickOptions;
  }

  /**
   * @return the seeConflictsOption
   */
  /*
  public JMenuItem getSeeConflictsOption()
  {
  return seeConflictsOption;
  }*/
  /**
   * @return the popupOptions
   */
  public Vector<JMenuItem> getPopupOptions()
  {
    return popupOptions;
  }

  /**
   * @return the selectedGroups
   */
  public Vector<CCPVGroupCircle> getSelectedGroups()
  {
    return selectedGroups;
  }

  /**
   * @return the menuOpen
   */
  public boolean isMenuOpen()
  {
    return menuOpen;
  }

  /**
   * @return the showingConflicts
   */
  public boolean isShowingConflicts()
  {
    return showingConflicts;
  }

  public void setShowingConflicts(boolean bool)
  {
    showingConflicts = bool;
    setAllProperColors();
  }

  /**
   * @return the conflictFocusCoalition
   */
  public int getConflictFocusCoalition()
  {
    return conflictFocusCoalition;
  }

  public void setConflictFocusCoalition(CoalitionCircleViewerCircle c)
  {
    conflictFocusCoalition = c.coalition;
  }

  public void setConflictFocusCoalition(int i)
  {
    conflictFocusCoalition = i;
  }

  /**
   * @return the menuTransparencyColor
   */
  public Color getMenuTransparencyColor()
  {
    return menuTransparencyColor;
  }

  /**
   * @return the allGroupCircles
   */
  public Vector<CCPVGroupCircle> getAllGroupCircles()
  {
    return allGroupCircles;
  }

  /**
   * @return the allCoalitionCircles
   */
  public Vector<CCPVCoalitionCircle> getAllCoalitionCircles()
  {
    return allCoalitionCircles;
  }

  /**
   * @return the animationScript
   */
  public Vector<HashMap<CCPVGroupCircle, Point>> getAnimationScript()
  {
    return animationScript;
  }

  /**
   * @return the animationIndex
   */
  public int getAnimationIndex()
  {
    return animationIndex;
  }

  /**
   * @return the animationTimer
   */
  public Timer getAnimationTimer()
  {
    return animationTimer;
  }

  /**
   * @return the animationStepInterval
   */
  public long getAnimationStepInterval()
  {
    return animationStepInterval;
  }

  /**
   * @return the animationTotalTime
   */
  public long getAnimationTotalTime()
  {
    return animationTotalTime;
  }
}
